/**
 * KopsoxFramework
 * Copyright (C) 2010 Free Software Foundation, Inc. <http://fsf.org/>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kopsox.environment.proxy;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

import org.kopsox.environment.ContainerEnvironment;
import org.kopsox.environment.KopsoxEnvironment;
import org.kopsox.environment.ValidatorEnvironment;
import org.kopsox.environment.ValueEnvironment;
import org.kopsox.environment.impl.ContainerEnvironmentImpl;
import org.kopsox.environment.impl.ValidatorEnvironmentImpl;
import org.kopsox.environment.impl.ValueEnvironmentImpl;
import org.kopsox.util.log.KopsoxLogger;
import org.kopsox.util.log.impl.SystemOutLoggerImpl;

/**
 * This Registry is used for managing different Environments (external modules).<br><br>
 * If you want to define your own Value/Container/ValidatorEnvironments and Logger, you have to define 
 * a SystemProperty with the name 'pathtoconfig', which is the path to the file kopsoxenvironment.properties.<br><br>
 * The Registry will try to load the properties-file first from the Classloader, if this fails, the Registry will try to load the properties from an file, if this
 * also fails, the Registry takes the fallback properties (org/kopsox/environment/proxy/config/kopsoxenvironment.properties).<br>
 * <br>
 * The configuration files of the Environments, must be specfied in an packages of your programm, because they are loaded with the help of the Classloader!<br>
 * 
 * @author Konrad Renner - 21.06.2010
 *
 */
public final class KopsoxRegistry {
	
	private Class<? extends KopsoxLogger> loggerImpl;
	private ValueEnvironment valueEnvironment;
	private ValidatorEnvironment validatorEnvironment;
	private ContainerEnvironment containerEnvironment;
	private Properties environmentProperties = null;
	
	//Marker if the Environments are initialized
	private volatile boolean environmentsInitialized = false;
	
	//Map which holds Custom Environments
	private final Map<String, KopsoxEnvironment> customEnvironments;

	private KopsoxRegistry(){
		this.customEnvironments = new ConcurrentHashMap<String, KopsoxEnvironment>();
	}
	
	public static final KopsoxRegistry INSTANCE = new KopsoxRegistry();
	
	/**
	 * Gets an Environment and initializeses it<br>
	 * Returns null if the Environment cannot be loaded<br>
	 * 
	 * @param <T> 
	 * @param environment  
	 * @return T
	 */
	public <T extends KopsoxEnvironment> KopsoxEnvironment getEnvironment(final Class<T> environment){
		if(environment == null) {
			return null;
		}
		
		if(this.customEnvironments.containsKey(environment.getName())) {
			return this.customEnvironments.get(environment.getName());
		}
		
		T instance;
		try {
			instance = environment.newInstance();
			instance.initialize(this.environmentProperties);
			
			this.customEnvironments.put(environment.getName(), instance);
			return instance;
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return null;
	}
	
	public Properties getKopsoxProperties() {
		if(this.environmentProperties == null) {
			this.environmentProperties = loadProperties();
		}
		return this.environmentProperties;
	}

	/**
	 * @return the valueEnvironment
	 */
	public ValueEnvironment getValueEnvironment() {
		
		if(!this.environmentsInitialized){
			initializeEnvironments();
		}
		
		return this.valueEnvironment;
	}

	/**
	 * @return the validatorEnvironment
	 */
	public ValidatorEnvironment getValidatorEnvironment() {
		
		if(!this.environmentsInitialized){
			initializeEnvironments();
		}
		
		return this.validatorEnvironment;
	}

	/**
	 * @return the containerEnvironment
	 */
	public ContainerEnvironment getContainerEnvironment() {
		
		if(!this.environmentsInitialized){
			initializeEnvironments();
		}
		
		return this.containerEnvironment;
	}


	/**
	 * Gets a Logger for the given Class
	 * 
	 * @param clazz
	 * @return KopsoxLogger
	 */
	public KopsoxLogger getKopsoxLoggerForClass(Class<?> clazz){
		KopsoxLogger ret = null;
		try{
			
			if(!this.environmentsInitialized) {
				this.initializeEnvironments();
			}
			
			Constructor<?> constructor = getConstructorWithClassArguement(clazz.getClass());
			
			if(constructor != null) {
			
				Object[] initargs = {clazz};
				
				return (KopsoxLogger) constructor.newInstance(initargs);
			}
			
			return this.loggerImpl.newInstance();
		}catch(Exception e){
			ret = new SystemOutLoggerImpl(clazz);
		}
		return ret;
	}
	
	
	private Constructor<?> getConstructorWithClassArguement(Class<?> clazz){
		try {
			return this.loggerImpl.getConstructor(clazz.getClass());
		} catch (SecurityException e) {
			//nothing
		} catch (NoSuchMethodException e) {
			//nothing
		}
		
		return null;
	}
	
	/**
	 * This method initializes the 3 standard environments
	 * 
	 * @return boolean
	 */
	public synchronized final boolean initializeEnvironments(){
		boolean ret = true;
		
		if(!this.environmentsInitialized){
			
			//Load the Properties
			this.environmentProperties = loadProperties();
			
			if(this.environmentProperties == null) {
				//If the properties could not be loaded, configure the Environments with the standard impls
				this.loggerImpl = SystemOutLoggerImpl.class;
				this.validatorEnvironment = new ValidatorEnvironmentImpl();
				this.valueEnvironment = new ValueEnvironmentImpl();
				this.containerEnvironment = new ContainerEnvironmentImpl();
			}else {
				this.loggerImpl = loadLogger(this.environmentProperties.getProperty(EnvironmentPropertiesEnum.LOGGER_CUSTOM.toString()));
				
				this.validatorEnvironment = loadValidatorEnvironment(this.environmentProperties.getProperty(EnvironmentPropertiesEnum.ENVIRONMENT_VALIDATOR.toString()));
				this.valueEnvironment = loadValueEnvironment(this.environmentProperties.getProperty(EnvironmentPropertiesEnum.ENVIRONMENT_VALUE.toString()));
				this.containerEnvironment = loadContainerEnvironment(this.environmentProperties.getProperty(EnvironmentPropertiesEnum.ENVIRONMENT_CONTAINER.toString()));
			}
			
			
			if(!this.validatorEnvironment.initialize(this.environmentProperties)){return false;}
			if(!this.valueEnvironment.initialize(this.environmentProperties)){return false;}
			if(!this.containerEnvironment.initialize(this.environmentProperties)){return false;}
		}
		
		return ret;
	}
	
	/**
	 * Loads the Properties from the 
	 */
	private final synchronized Properties loadProperties() {
		Properties properties = new Properties();
		String path = System.getProperty(EnvironmentPropertiesEnum.PROPERTY_PATH_TO_CONFIG.toString());
		
		InputStream stream = null;
		
		if(path != null) {
			
			//First: try to get the properties-file via ClassLoader
			stream = this.getClass().getClassLoader().getResourceAsStream(path);
			
			if(stream != null) {
				try {
					properties.load(stream);
					stream.close();
					
					return properties;
				} catch (IOException e) {
					e.printStackTrace();
				}
			}else {
			
				//Second: check if the file is on the file-system
				try {
					stream = new BufferedInputStream(new FileInputStream(path));
					properties.load(stream);
					stream.close();
					
					return properties;
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
		
		//Fallback-Implementation
		try {
			InputStream str = this.getClass().getResourceAsStream("config/kopsoxenvironment.properties");
			properties.load(str);
			str.close();
			
			return properties;
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return null;
	}
	
	/**
	 * Loads the Logger
	 * 
	 * @param className
	 * @return Class
	 */
	@SuppressWarnings("unchecked")
	private final Class<? extends KopsoxLogger> loadLogger(String className){
		if(className == null) {
			return SystemOutLoggerImpl.class;
		}
		
		try {
			return (Class) Class.forName(className);
		}catch(Exception ce) {
			ce.printStackTrace();
			return SystemOutLoggerImpl.class;
		}
	}
	
	/**
	 * Loads the ContainerEnvironment
	 * 
	 * @param className
	 * @return ContainerEnvironment
	 */
	private final ContainerEnvironment loadContainerEnvironment(String className) {
		if(className == null) {
			return new ContainerEnvironmentImpl();
		}
		
		try {
			return (ContainerEnvironment) Class.forName(className).newInstance();
		}catch (Exception e) {
			e.printStackTrace();
			return new ContainerEnvironmentImpl();
		}
	}
	
	/**
	 * Loads the ValueEnvironment
	 * 
	 * @param className
	 * @return ValueEnvironment
	 */
	private final ValueEnvironment loadValueEnvironment(String className) {
		if(className == null) {
			return new ValueEnvironmentImpl();
		}
		
		try {
			return (ValueEnvironmentImpl) Class.forName(className).newInstance();
		}catch (Exception e) {
			e.printStackTrace();
			return new ValueEnvironmentImpl();
		}
	}

	/**
	 * Loads the ValidatorEnvironment
	 * 
	 * @param className
	 * @return ValidatorEnvironment
	 */
	private final ValidatorEnvironment loadValidatorEnvironment(String className) {
		if(className == null) {
			return new ValidatorEnvironmentImpl();
		}
		
		try {
			return (ValidatorEnvironmentImpl) Class.forName(className).newInstance();
		}catch (Exception e) {
			e.printStackTrace();
			return new ValidatorEnvironmentImpl();
		}
	}
}
